<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>安卓自定义View进阶-Canvas之画布操作</title>
  <meta name="description" content="Canvas之画布操作，讲解画布变换相关内容，主要包括：平移，缩放，旋转，错切，以及画布状态保存与回滚。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="画布操作, Canvas, translate, scale, rotate, skew, save, restore, saveLayer, restoreToCount, getSaveCount, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  <meta name="关键字" content="画布操作, Canvas, translate, scale, rotate, skew, save, restore, saveLayer, restoreToCount, getSaveCount, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="安卓自定义View进阶-Canvas之画布操作">
  <meta name="twitter:description" content="Canvas之画布操作，讲解画布变换相关内容，主要包括：平移，缩放，旋转，错切，以及画布状态保存与回滚。">
  <meta name="twitter:keywords" content="画布操作, Canvas, translate, scale, rotate, skew, save, restore, saveLayer, restoreToCount, getSaveCount, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="安卓自定义View进阶-Canvas之画布操作">
  <meta property="og:description" content="Canvas之画布操作，讲解画布变换相关内容，主要包括：平移，缩放，旋转，错切，以及画布状态保存与回滚。">
  <meta name="og:keywords" content="画布操作, Canvas, translate, scale, rotate, skew, save, restore, saveLayer, restoreToCount, getSaveCount, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/customview/Canvas_Convert">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2016-01-28 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2016-01-28</time> &#8226; <span class="post-meta__tags tags">自定义View,Canvas</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">安卓自定义View进阶-Canvas之画布操作</h1>
  </header>

  <section class="post">
    <p>上一篇<a href="http://www.gcssloop.com/customview/Canvas_BasicGraphics" title="Canvas之绘制基本图形">Canvas之绘制基本图形</a>中我们了解了如何使用Canvas绘制基本图形，本次了解一些基本的画布操作。</p>

<p>本来想把画布操作放到后面部分的，但是发现很多图形绘制都离不开画布操作，于是先讲解一下画布的基本操作方法。</p>

<h2 id="一canvas的常用操作速查表">一.Canvas的常用操作速查表</h2>

<table>
  <thead>
    <tr>
      <th>操作类型</th>
      <th>相关API</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>绘制颜色</td>
      <td>drawColor, drawRGB, drawARGB</td>
      <td>使用单一颜色填充整个画布</td>
    </tr>
    <tr>
      <td>绘制基本形状</td>
      <td>drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc</td>
      <td>依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧</td>
    </tr>
    <tr>
      <td>绘制图片</td>
      <td>drawBitmap, drawPicture</td>
      <td>绘制位图和图片</td>
    </tr>
    <tr>
      <td>绘制文本</td>
      <td>drawText,    drawPosText, drawTextOnPath</td>
      <td>依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字</td>
    </tr>
    <tr>
      <td>绘制路径</td>
      <td>drawPath</td>
      <td>绘制路径，绘制贝塞尔曲线时也需要用到该函数</td>
    </tr>
    <tr>
      <td>顶点操作</td>
      <td>drawVertices, drawBitmapMesh</td>
      <td>通过对顶点操作可以使图像形变，drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用</td>
    </tr>
    <tr>
      <td>画布剪裁</td>
      <td>clipPath,    clipRect</td>
      <td>设置画布的显示区域</td>
    </tr>
    <tr>
      <td>画布快照</td>
      <td>save, restore, saveLayerXxx, restoreToCount, getSaveCount</td>
      <td>依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数</td>
    </tr>
    <tr>
      <td>画布变换</td>
      <td>translate, scale, rotate, skew</td>
      <td>依次为 位移、缩放、 旋转、错切</td>
    </tr>
    <tr>
      <td>Matrix(矩阵)</td>
      <td>getMatrix, setMatrix, concat</td>
      <td>实际上画布的位移，缩放等操作的都是图像矩阵Matrix， 只不过Matrix比较难以理解和使用，故封装了一些常用的方法。</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="二canvas基本操作">二.Canvas基本操作</h2>

<h3 id="1画布操作">1.画布操作</h3>

<h4 id="为什么要有画布操作">为什么要有画布操作？</h4>

<p>画布操作可以帮助我们用更加容易理解的方式制作图形。</p>

<p>例如： 从坐标原点为起点，绘制一个长度为20dp，与水平线夹角为30度的线段怎么做？</p>

<p>按照我们通常的想法(<em>被常年训练出来的数学思维</em>)，就是先使用三角函数计算出线段结束点的坐标，然后调用drawLine即可。</p>

<p>然而这是否是被固有思维禁锢了？</p>

<p>假设我们先绘制一个长度为20dp的水平线，然后将这条水平线旋转30度，则最终看起来效果是相同的，而且不用进行三角函数计算，这样是否更加简单了一点呢？</p>

<p><strong>合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果，这也是画布操作存在的原因。</strong></p>

<p><strong>PS: 所有的画布操作都只影响后续的绘制，对之前已经绘制过的内容没有影响。</strong></p>

<hr />

<h4 id="位移translate">⑴位移(translate)</h4>

<p>translate是坐标系的移动，可以为图形绘制选择一个合适的坐标系。
<strong>请注意，位移是基于当前位置移动，而不是每次基于屏幕左上角的(0,0)点移动</strong>，如下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 省略了创建画笔的代码</span>

<span class="c1">// 在坐标原点绘制一个黑色圆形</span>
<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">100</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="c1">// 在坐标原点绘制一个蓝色圆形</span>
<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">100</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071630.jpg?gcssloop" alt="" /></p>

<p>我们首先将坐标系移动一段距离绘制一个圆形，之后再移动一段距离绘制一个圆形，<strong>两次移动是可叠加的</strong>。</p>

<hr />

<h4 id="缩放scale">⑵缩放(scale)</h4>

<p>缩放提供了两个方法，如下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">scale</span> <span class="o">(</span><span class="kt">float</span> <span class="n">sx</span><span class="o">,</span> <span class="kt">float</span> <span class="n">sy</span><span class="o">)</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">scale</span> <span class="o">(</span><span class="kt">float</span> <span class="n">sx</span><span class="o">,</span> <span class="kt">float</span> <span class="n">sy</span><span class="o">,</span> <span class="kt">float</span> <span class="n">px</span><span class="o">,</span> <span class="kt">float</span> <span class="n">py</span><span class="o">)</span>
</code></pre>
</div>

<p>这两个方法中前两个参数是相同的分别为x轴和y轴的缩放比例。而第二种方法比前一种多了两个参数，用来控制缩放中心位置的。</p>

<p>缩放比例(sx,sy)取值范围详解：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">取值范围(n)</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">(-∞, -1)</td>
      <td>先根据缩放中心放大n倍，再根据中心轴进行翻转</td>
    </tr>
    <tr>
      <td style="text-align: center">-1</td>
      <td>根据缩放中心轴进行翻转</td>
    </tr>
    <tr>
      <td style="text-align: center">(-1, 0)</td>
      <td>先根据缩放中心缩小到n，再根据中心轴进行翻转</td>
    </tr>
    <tr>
      <td style="text-align: center">0</td>
      <td>不会显示，若sx为0，则宽度为0，不会显示，sy同理</td>
    </tr>
    <tr>
      <td style="text-align: center">(0, 1)</td>
      <td>根据缩放中心缩小到n</td>
    </tr>
    <tr>
      <td style="text-align: center">1</td>
      <td>没有变化</td>
    </tr>
    <tr>
      <td style="text-align: center">(1, +∞)</td>
      <td>根据缩放中心放大n倍</td>
    </tr>
  </tbody>
</table>

<p>如果在缩放时稍微注意一下就会发现<strong>缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴</strong>，如下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(</span><span class="mf">0.5f</span><span class="o">,</span><span class="mf">0.5f</span><span class="o">);</span>                <span class="c1">// 画布缩放</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p>(为了更加直观，我添加了一个坐标系，可以比较明显的看出，缩放中心就是坐标原点)</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71631.jpg?gcssloop" alt="" /></p>

<p>接下来我们使用第二种方法让缩放中心位置稍微改变一下，如下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(</span><span class="mf">0.5f</span><span class="o">,</span><span class="mf">0.5f</span><span class="o">,</span><span class="mi">200</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>          <span class="c1">// 画布缩放  &lt;-- 缩放中心向右偏移了200个单位</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p>(图中用箭头指示的就是缩放中心。)</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071632.jpg?gcssloop" alt="" /></p>

<p>前面两个示例缩放的数值都是正数，按照表格中的说明，<strong>当缩放比例为负数的时候会根据缩放中心轴进行翻转</strong>，下面我们就来实验一下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>


<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(-</span><span class="mf">0.5f</span><span class="o">,-</span><span class="mf">0.5f</span><span class="o">);</span>          <span class="c1">// 画布缩放</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071634.jpg?gcssloop" alt="" /></p>

<blockquote>
  <p>为了效果明显，这次我不仅添加了坐标系而且对矩形中几个重要的点进行了标注，具有相同字母标注的点是一一对应的。</p>
</blockquote>

<p>由于本次未对缩放中心进行偏移，所有默认的缩放中心就是坐标原点，中心轴就是x轴和y轴。</p>

<p>本次缩放可以看做是先根据缩放中心(坐标原点)缩放到原来的0.5倍，然后分别按照x轴和y轴进行翻转。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(-</span><span class="mf">0.5f</span><span class="o">,-</span><span class="mf">0.5f</span><span class="o">,</span><span class="mi">200</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>          <span class="c1">// 画布缩放  &lt;-- 缩放中心向右偏移了200个单位</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071635.jpg?gcssloop" alt="" /></p>

<blockquote>
  <p>添加了这么多的辅助内容，希望大家能够看懂。</p>
</blockquote>

<p>本次对缩放中心点y轴坐标进行了偏移，故中心轴也向右偏移了。</p>

<p><b>PS:和位移(translate)一样，缩放也是可以叠加的。</b></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(</span><span class="mf">0.5f</span><span class="o">,</span><span class="mf">0.5f</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(</span><span class="mf">0.5f</span><span class="o">,</span><span class="mf">0.1f</span><span class="o">);</span>
</code></pre>
</div>

<p>调用两次缩放则 x轴实际缩放为0.5x0.5=0.25 y轴实际缩放为0.5x0.1=0.05</p>

<p>下面我们利用这一特性制作一个有趣的图形。</p>

<blockquote>
  <p>注意设置画笔模式为描边(STROKE)</p>
</blockquote>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(-</span><span class="mi">400</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">&lt;=</span><span class="mi">20</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
<span class="o">{</span>
	<span class="n">canvas</span><span class="o">.</span><span class="na">scale</span><span class="o">(</span><span class="mf">0.9f</span><span class="o">,</span><span class="mf">0.9f</span><span class="o">);</span>
	<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
<span class="o">}</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71640.jpg?gcssloop" alt="" /></p>

<hr />

<h4 id="旋转rotate">⑶旋转(rotate)</h4>

<p>旋转提供了两种方法：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">rotate</span> <span class="o">(</span><span class="kt">float</span> <span class="n">degrees</span><span class="o">)</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">rotate</span> <span class="o">(</span><span class="kt">float</span> <span class="n">degrees</span><span class="o">,</span> <span class="kt">float</span> <span class="n">px</span><span class="o">,</span> <span class="kt">float</span> <span class="n">py</span><span class="o">)</span>
</code></pre>
</div>

<p>和缩放一样，第二种方法多出来的两个参数依旧是控制旋转中心点的。</p>

<p>默认的旋转中心依旧是坐标原点：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">rotate</span><span class="o">(</span><span class="mi">180</span><span class="o">);</span>                     <span class="c1">// 旋转180度 &lt;-- 默认旋转中心为原点</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071640.jpg?gcssloop" alt="" /></p>

<p>改变旋转中心位置：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,-</span><span class="mi">400</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">rotate</span><span class="o">(</span><span class="mi">180</span><span class="o">,</span><span class="mi">200</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>               <span class="c1">// 旋转180度 &lt;-- 旋转中心向右偏移200个单位</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071642.jpg?gcssloop" alt="" /></p>

<p><b>好吧，旋转也是可叠加的</b></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">rotate</span><span class="o">(</span><span class="mi">180</span><span class="o">);</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">rotate</span><span class="o">(</span><span class="mi">20</span><span class="o">);</span>
</code></pre>
</div>

<p>调用两次旋转，则实际的旋转角度为180+20=200度。</p>

<p>为了演示这一个效果，我做了一个不明觉厉的东西：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>          <span class="c1">// 绘制两个圆形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">380</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">&lt;=</span><span class="mi">360</span><span class="o">;</span> <span class="n">i</span><span class="o">+=</span><span class="mi">10</span><span class="o">){</span>               <span class="c1">// 绘制圆形之间的连接线</span>
   <span class="n">canvas</span><span class="o">.</span><span class="na">drawLine</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">380</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">400</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
   <span class="n">canvas</span><span class="o">.</span><span class="na">rotate</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>
<span class="o">}</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071643.jpg?gcssloop" alt="" /></p>

<hr />

<h4 id="错切skew">⑷错切(skew)</h4>

<p>skew这里翻译为错切，错切是特殊类型的线性变换。</p>

<p>错切只提供了一种方法：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">skew</span> <span class="o">(</span><span class="kt">float</span> <span class="n">sx</span><span class="o">,</span> <span class="kt">float</span> <span class="n">sy</span><span class="o">)</span>
</code></pre>
</div>

<p><b>参数含义：<br />
float sx:将画布在x方向上倾斜相应的角度，sx倾斜角度的tan值，<br />
float sy:将画布在y轴方向上倾斜相应的角度，sy为倾斜角度的tan值.</b></p>

<p>变换后:</p>

<div class="highlighter-rouge"><pre class="highlight"><code>X = x + sx * y
Y = sy * x + y
</code></pre>
</div>

<p>示例：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">200</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">skew</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>                       <span class="c1">// 水平错切 &lt;- 45度</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071644.jpg?gcssloop" alt="" /></p>

<p><b>如你所想，错切也是可叠加的，不过请注意，调用次序不同绘制结果也会不同</b></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 将坐标系原点移动到画布正中心</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>

<span class="n">RectF</span> <span class="n">rect</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RectF</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">0</span><span class="o">,</span><span class="mi">200</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>   <span class="c1">// 矩形区域</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">);</span>           <span class="c1">// 绘制黑色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">skew</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>                       <span class="c1">// 水平错切</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">skew</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">1</span><span class="o">);</span>                       <span class="c1">// 垂直错切</span>

<span class="n">mPaint</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLUE</span><span class="o">);</span>            <span class="c1">// 绘制蓝色矩形</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawRect</span><span class="o">(</span><span class="n">rect</span><span class="o">,</span><span class="n">mPaint</span><span class="o">);</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071645.jpg?gcssloop" alt="" /></p>

<hr />

<h4 id="快照save和回滚restore">⑸快照(save)和回滚(restore)</h4>

<p><strong>Q: 为什么存在快照与回滚</strong><br />
A：画布的操作是不可逆的，而且很多画布操作会影响后续的步骤，例如第一个例子，两个圆形都是在坐标原点绘制的，而因为坐标系的移动绘制出来的实际位置不同。所以会对画布的一些状态进行保存和回滚。</p>

<p><strong>与之相关的API:</strong></p>

<table>
  <thead>
    <tr>
      <th>相关API</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>save</td>
      <td>把当前的画布的状态进行保存，然后放入特定的栈中</td>
    </tr>
    <tr>
      <td>saveLayerXxx</td>
      <td>新建一个图层，并放入特定的栈中</td>
    </tr>
    <tr>
      <td>restore</td>
      <td>把栈中最顶层的画布状态取出来，并按照这个状态恢复当前的画布</td>
    </tr>
    <tr>
      <td>restoreToCount</td>
      <td>弹出指定位置及其以上所有的状态，并按照指定位置的状态进行恢复</td>
    </tr>
    <tr>
      <td>getSaveCount</td>
      <td>获取栈中内容的数量(即保存次数)</td>
    </tr>
  </tbody>
</table>

<p>下面对其中的一些概念和方法进行分析：</p>

<h5 id="状态栈">状态栈：</h5>

<p>其实这个栈我也不知道叫什么名字，暂时叫做状态栈吧，它看起来像下面这样：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071646.jpg?gcssloop" alt="" /></p>

<p>这个栈可以存储画布状态和图层状态。</p>

<p><strong>Q：什么是画布和图层？</strong><br />
A：实际上我们看到的画布是由多个图层构成的，如下图(图片来自网络)：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071648.jpg?gcssloop" alt="" /></p>

<p>实际上我们之前讲解的绘制操作和画布操作都是在默认图层上进行的。<br />
在通常情况下，使用默认图层就可满足需求，但是如果需要绘制比较复杂的内容，如地图(地图可以有多个地图层叠加而成，比如：政区层，道路层，兴趣点层)等，则分图层绘制比较好一些。<br />
你可以把这些图层看做是一层一层的玻璃板，你在每层的玻璃板上绘制内容，然后把这些玻璃板叠在一起看就是最终效果。</p>

<h5 id="saveflags">SaveFlags</h5>

<table>
  <thead>
    <tr>
      <th>名称</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ALL_SAVE_FLAG</td>
      <td>默认，保存全部状态</td>
    </tr>
    <tr>
      <td>CLIP_SAVE_FLAG</td>
      <td>保存剪辑区</td>
    </tr>
    <tr>
      <td>CLIP_TO_LAYER_SAVE_FLAG</td>
      <td>剪裁区作为图层保存</td>
    </tr>
    <tr>
      <td>FULL_COLOR_LAYER_SAVE_FLAG</td>
      <td>保存图层的全部色彩通道</td>
    </tr>
    <tr>
      <td>HAS_ALPHA_LAYER_SAVE_FLAG</td>
      <td>保存图层的alpha(不透明度)通道</td>
    </tr>
    <tr>
      <td>MATRIX_SAVE_FLAG</td>
      <td>保存Matrix信息( translate, rotate, scale, skew)</td>
    </tr>
  </tbody>
</table>

<h5 id="save">save</h5>

<p>save 有两种方法：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 保存全部状态</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">save</span> <span class="o">()</span>

<span class="c1">// 根据saveFlags参数保存一部分状态</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">save</span> <span class="o">(</span><span class="kt">int</span> <span class="n">saveFlags</span><span class="o">)</span>
</code></pre>
</div>

<p>可以看到第二种方法比第一种多了一个saveFlags参数，使用这个参数可以只保存一部分状态，更加灵活，这个saveFlags参数具体可参考上面表格中的内容。</p>

<p>每调用一次save方法，都会在栈顶添加一条状态信息，以上面状态栈图片为例，再调用一次save则会在第5次上面载添加一条状态。</p>

<h4 id="savelayerxxx">saveLayerXxx</h4>

<p>saveLayerXxx有比较多的方法：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 无图层alpha(不透明度)通道</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayer</span> <span class="o">(</span><span class="n">RectF</span> <span class="n">bounds</span><span class="o">,</span> <span class="n">Paint</span> <span class="n">paint</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayer</span> <span class="o">(</span><span class="n">RectF</span> <span class="n">bounds</span><span class="o">,</span> <span class="n">Paint</span> <span class="n">paint</span><span class="o">,</span> <span class="kt">int</span> <span class="n">saveFlags</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayer</span> <span class="o">(</span><span class="kt">float</span> <span class="n">left</span><span class="o">,</span> <span class="kt">float</span> <span class="n">top</span><span class="o">,</span> <span class="kt">float</span> <span class="n">right</span><span class="o">,</span> <span class="kt">float</span> <span class="n">bottom</span><span class="o">,</span> <span class="n">Paint</span> <span class="n">paint</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayer</span> <span class="o">(</span><span class="kt">float</span> <span class="n">left</span><span class="o">,</span> <span class="kt">float</span> <span class="n">top</span><span class="o">,</span> <span class="kt">float</span> <span class="n">right</span><span class="o">,</span> <span class="kt">float</span> <span class="n">bottom</span><span class="o">,</span> <span class="n">Paint</span> <span class="n">paint</span><span class="o">,</span> <span class="kt">int</span> <span class="n">saveFlags</span><span class="o">)</span>

<span class="c1">// 有图层alpha(不透明度)通道</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayerAlpha</span> <span class="o">(</span><span class="n">RectF</span> <span class="n">bounds</span><span class="o">,</span> <span class="kt">int</span> <span class="n">alpha</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayerAlpha</span> <span class="o">(</span><span class="n">RectF</span> <span class="n">bounds</span><span class="o">,</span> <span class="kt">int</span> <span class="n">alpha</span><span class="o">,</span> <span class="kt">int</span> <span class="n">saveFlags</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayerAlpha</span> <span class="o">(</span><span class="kt">float</span> <span class="n">left</span><span class="o">,</span> <span class="kt">float</span> <span class="n">top</span><span class="o">,</span> <span class="kt">float</span> <span class="n">right</span><span class="o">,</span> <span class="kt">float</span> <span class="n">bottom</span><span class="o">,</span> <span class="kt">int</span> <span class="n">alpha</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">saveLayerAlpha</span> <span class="o">(</span><span class="kt">float</span> <span class="n">left</span><span class="o">,</span> <span class="kt">float</span> <span class="n">top</span><span class="o">,</span> <span class="kt">float</span> <span class="n">right</span><span class="o">,</span> <span class="kt">float</span> <span class="n">bottom</span><span class="o">,</span> <span class="kt">int</span> <span class="n">alpha</span><span class="o">,</span> <span class="kt">int</span> <span class="n">saveFlags</span><span class="o">)</span>
</code></pre>
</div>

<p><b>注意：saveLayerXxx方法会让你花费更多的时间去渲染图像(图层多了相互之间叠加会导致计算量成倍增长)，使用前请谨慎，如果可能，尽量避免使用。</b></p>

<p>使用saveLayerXxx方法，也会将图层状态也放入状态栈中，同样使用restore方法进行恢复。</p>

<p>这个暂时不过多讲述，如果以后用到详细讲解。(因为这里面东西也有不少啊QAQ)</p>

<h5 id="restore">restore</h5>

<p>状态回滚，就是从栈顶取出一个状态然后根据内容进行恢复。</p>

<p>同样以上面状态栈图片为例，调用一次restore方法则将状态栈中第5次取出，根据里面保存的状态进行状态恢复。</p>

<h5 id="restoretocount">restoreToCount</h5>

<p>弹出指定位置以及以上所有状态，并根据指定位置状态进行恢复。</p>

<p>以上面状态栈图片为例，如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态，并根据第2次保存的状态进行恢复。</p>

<h5 id="getsavecount">getSaveCount</h5>

<p>获取保存的次数，即状态栈中保存状态的数量，以上面状态栈图片为例，使用该函数的返回值为5。</p>

<p>不过请注意，该函数的最小返回值为1，即使弹出了所有的状态，返回值依旧为1，代表默认状态。</p>

<h5 id="常用格式">常用格式</h5>

<p>虽然关于状态的保存和回滚啰嗦了不少，不过大多数情况下只需要记住下面的步骤就可以了：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">save</span><span class="o">();</span>      <span class="c1">//保存状态</span>
<span class="o">...</span>          <span class="c1">//具体操作</span>
<span class="n">restore</span><span class="o">();</span>   <span class="c1">//回滚到之前的状态</span>
</code></pre>
</div>

<p>这种方式也是最简单和最容易理解的使用方法。</p>

<hr />

<h2 id="三总结">三.总结</h2>

<p>如本文一开始所说，合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果。</p>

<p>(,,• ₃ •,,)</p>

<p><strong>PS: 由于本人英文水平有限，某些地方可能存在误解或词语翻译不准确，如果你对此有疑问可以提交Issues进行反馈。</strong></p>

<h2 id="about">About</h2>

<p><a href="http://www.gcssloop.com/customview/CustomViewIndex">本系列相关文章</a></p>

<p>作者微博: <a href="http://weibo.com/GcsSloop">GcsSloop</a></p>

<hr />

<h2 id="四参考资料">四.参考资料</h2>

<p><a href="http://developer.android.com/reference/android/graphics/Canvas.html">Canvas</a><br />
<a href="http://blog.csdn.net/harvic880925/article/details/39080931">canvas变换与操作</a><br />
<a href="http://blog.csdn.net/tianjian4592/article/details/45234419">Canvas之translate、scale、rotate、skew方法讲解</a><br />
<a href="http://www.cnblogs.com/liangstudyhome/p/4143498.html">Canvas的save(),saveLayer()和restore()浅谈</a><br />
<a href="http://www.programgo.com/article/72302404062/;jsessionid=8E62016408BFFB21D46F9C878A49D8EE">Graphics-&gt;Layers</a></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/customview/Canvas_PictureText" title="link to 安卓自定义View进阶-Canvas之图片文字">安卓自定义View进阶-Canvas之图片文字</a></h2>
    <p class="excerpt">在上一篇文章Canvas之画布操作中我们了解了画布的一些基本操作方法，本次了解一些绘制图片文字相关的内容。如果你对前几篇文章讲述的内容熟练掌握的话，那么恭喜你，本篇结束之后，大部分的自定义Vie...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2016-02-12 00:00:00 +0800" class="post-list__meta--date date">2016-02-12</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/Canvas_PictureText>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/customview/Canvas_BasicGraphics" title="link to 安卓自定义View进阶-Canvas之绘制图形">安卓自定义View进阶-Canvas之绘制图形</a></h2>
       <p class="excerpt">在上一篇自定义View分类与流程中我们了解自定义View相关的基本知识，不过，这些东西依旧还是理论，并不能拿来(zhuang)用(B), 这一次我们就了解一些能(zhaung)用(B)的东西。在...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2016-01-21 00:00:00 +0800" class="post-list__meta--date date">2016-01-21</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/Canvas_BasicGraphics>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
